package nessie;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.filechooser.FileFilter;

/**
 * the GUI(and visual debugging) handler of the application.
 */
public class MainUI {

    public MainUI(JFrame jFrame) {
        this.jFrame = jFrame;
        init();
    }
    private JFrame jFrame = null;

    private void init() {
        rescale();
    }
    private BufferedImage image;
    private BufferStrategy bufferStrategy;
    private WritableRaster writableRaster;
    private Graphics graphics;
    private int scaleRatio = 1, scale = 0;
    private static final int WIDTH = 256, HEIGHT = 240;
    private int[] backgroundRaster = new int[getWIDTH() * getHEIGHT()];
    /**the only purpose of this buffer is for checking the sprite0hit*/
    private final int[] transparencyBuffer = new int[(WIDTH + 8) * (HEIGHT + 8)];

    /**here we draw to a separate NON SCALED buffer to check for sprite0 hits*/
    public void drawToTransparencyBuffer(int x, int y, int color) {
        if (x < 0 || x >= WIDTH
                || y < 0 || y >= HEIGHT) {
            return;
        }
        transparencyBuffer[x + (y * WIDTH)] = color;
        plotToBuffer(x, y, color);
    }

    /**check if pixel to be drawn is in range*/
    public void drawToBuffer(int x, int y, int spriteColor) {
        if (x < 0 || x >= WIDTH
                || y < 0 || y >= HEIGHT) {
            return;
        }
        plotToBuffer(x, y, spriteColor);
    }

    /**draws the actual pixel(scaled) to the buffer*/
    private void plotToBuffer(int x, int y, int spriteColor) {
        if (x < WIDTH && y < HEIGHT) {
            for (int aX = 0; aX < scaleRatio; aX++) {
                for (int aY = 0; aY < scaleRatio; aY++) {
                    backgroundRaster[((x * scaleRatio) + aX) + (((y * scaleRatio) + aY) * getWIDTH())] = spriteColor;
                }
            }
        }
    }

    /**draws the buffer to screen, then clears it*/
    public void drawRasterToScreen() {
        {
            //copy buffer to screen buffer
            int[] dataBuffer = ((DataBufferInt) (writableRaster.getDataBuffer())).getData();
            System.arraycopy(backgroundRaster, 0, dataBuffer, 0, backgroundRaster.length);
            //clear
            Arrays.fill(transparencyBuffer, 0);
        }
        graphics = bufferStrategy.getDrawGraphics();
        graphics.drawImage(image, 0, 0, null);
        graphics.dispose();
        bufferStrategy.show();

        fps();
        if (scale != 0) {//resize the raster after drawing
            scaleRatio += (scale > 0 ? 1 : -1);
            scale = 0;
            rescale();
        }
    }
    private int backgroundColor = Color.BLACK.getRGB();

    public void setBackgroundColor(int backgroundColor) {
        this.backgroundColor = backgroundColor;
        Arrays.fill(backgroundRaster, this.backgroundColor);//paint it black
    }

    /**
     * this function checks the transparency buffer which is filled as the
     * background is drawn.
     */
    public boolean isBackgroundPixelOpaque(int x, int y) {
        int backgroundPixel = transparencyBuffer[x + (y * WIDTH)];
        if ((backgroundPixel != 0)) {//background NOT transparent
            return true;
        }
        return false;
    }

    /**re-initializes the frame with the rescaled(if any) dimensions*/
    private void rescale() {
        backgroundRaster = new int[getWIDTH() * getHEIGHT()];

        locationX = jFrame.getLocation().x;
        locationY = jFrame.getLocation().y;
        jFrame.dispose();
        jFrame = new JFrame();
        jFrame.setResizable(false);
        jFrame.setIgnoreRepaint(true);
        jFrame.setFocusable(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.addKeyListener(keyListener);
        jFrame.setUndecorated(false);


        Canvas canvas = new Canvas();
        canvas.setIgnoreRepaint(true);
        canvas.setSize(getWIDTH(), getHEIGHT());
        canvas.setEnabled(true);

        jFrame.add(canvas);
        jFrame.pack();
        jFrame.setVisible(true);

        canvas.createBufferStrategy(2);
        bufferStrategy = canvas.getBufferStrategy();

        GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice device = environment.getDefaultScreenDevice();
        GraphicsConfiguration configuration = device.getDefaultConfiguration();

        image = configuration.createCompatibleImage(getWIDTH(), getWIDTH());
        writableRaster = image.getRaster();

        jFrame.setLocation(locationX, locationY);
        jFrame.requestFocus();
    }
    private int locationX, locationY;

    /**save current screen frame to disk*/
    public void dumpScreenBuffer(String filename) {
        if (filename == null || filename.isEmpty()) {
            filename = "C:/temp/imagedump_" + new Date().getTime() + ".png";
        }
        try {
            ImageIO.write(image, "PNG", new File(filename));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    private int frameCount = 0;
    private long fpsStartTime = System.nanoTime();
    private String mode = "NTSC";

    /**calculate and frames per second*/
    private void fps() {
        frameCount++;
        if (fpsStartTime + 1000000000 <= System.nanoTime()) {
            fpsStartTime = System.nanoTime();
            jFrame.setTitle((frameCount) + "fps    " + mode);// + "      blargg=" + Integer.toHexString(Memory.readByte(0x6000) & 0xFF));
            frameCount = 0;

//            if ((Memory.readByte(0x6000) & 0xFF) != 0x80) {
//                int $6004 = 0x6004;
//                int a = 0;
//                String blargg6004 = "";
//                while ((a = Memory.readByte($6004++)) != 0) {
//                    blargg6004 += (char) a;
//                }
//                System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
//                System.out.println(blargg6004);
//                System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
//            }
        }
    }

    private void forceFPS() {
        int $6004 = 0x6004;
        int a = 0;
        String blargg6004 = "";
        while ((a = Memory.readByte($6004++)) != 0) {
            blargg6004 += (char) a;
        }
        System.err.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        System.err.println(blargg6004);
        System.err.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
//        for (int y1 = 0; y1 < 240; y1++) {
//            for (int x1 = 0; x1 < 256; x1++) {
//                String hexString = Integer.toHexString(transparencyBuffer[x1 + (y1 * 256)]);
//                System.out.printf(hexString.substring(0, 1));
//            }
//        }
        System.err.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

    }
    protected static byte pressedKeysBitmask = 0;
    private KeyListener keyListener = new KeyListener() {//TODO:key bindings

        @Override
        public void keyTyped(KeyEvent e) {
        }

        @Override
        public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
//                forceFPS();
                System.exit(0);
            }
            switch (e.getKeyCode()) {
                case KeyEvent.VK_A://A
                    pressedKeysBitmask |= 1;
                    break;
                case KeyEvent.VK_B://B
                    pressedKeysBitmask |= 2;
                    break;
                case KeyEvent.VK_Q://SELECT
                    pressedKeysBitmask |= 4;
                    break;
                case KeyEvent.VK_W://START
                    pressedKeysBitmask |= 8;
                    break;
                case KeyEvent.VK_UP://UP
                    pressedKeysBitmask |= 16;
                    pressedKeysBitmask &= ~32;
                    break;
                case KeyEvent.VK_DOWN://DOWN
                    pressedKeysBitmask |= 32;
                    pressedKeysBitmask &= ~16;
                    break;
                case KeyEvent.VK_LEFT://LEFT
                    pressedKeysBitmask |= 64;
                    pressedKeysBitmask &= ~128;
                    break;
                case KeyEvent.VK_RIGHT://RIGHT
                    pressedKeysBitmask |= 128;
                    pressedKeysBitmask &= ~64;
                    break;
                case KeyEvent.VK_F11:
                    Main.switchModes();
                    if (mode.equals("PAL")) {
                        mode = "NTSC";
                    } else {
                        mode = "PAL";
                    }
                    break;
                case KeyEvent.VK_F12:
                    dumpScreenBuffer(null);
                    break;
                case KeyEvent.VK_F8:
                    Main.switchFramerateLimit();
                    break;
                case KeyEvent.VK_PAGE_UP:
                    ++scale;
                    break;
                case KeyEvent.VK_PAGE_DOWN:
                    if (scaleRatio > 1) {
                        --scale;
                    }
                    break;
                default:
                    break;
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_A://A
                    pressedKeysBitmask &= ~1;
                    break;
                case KeyEvent.VK_B://B
                    pressedKeysBitmask &= ~2;
                    break;
                case KeyEvent.VK_Q://SELECT
                    pressedKeysBitmask &= ~4;
                    break;
                case KeyEvent.VK_W://START
                    pressedKeysBitmask &= ~8;
                    break;
                case KeyEvent.VK_UP://UP
                    pressedKeysBitmask &= ~16;
                    break;
                case KeyEvent.VK_DOWN://DOWN
                    pressedKeysBitmask &= ~32;
                    break;
                case KeyEvent.VK_LEFT://LEFT
                    pressedKeysBitmask &= ~64;
                    break;
                case KeyEvent.VK_RIGHT://RIGHT
                    pressedKeysBitmask &= ~128;
                    break;
                default:
                    break;
            }
        }
    };

    private int getHEIGHT() {
        return HEIGHT * scaleRatio;
    }

    private int getWIDTH() {
        return WIDTH * scaleRatio;
    }
}

class FileChooser extends JFrame {

    private JFileChooser jFileChooser;

    public FileChooser() {
        jFileChooser = new JFileChooser();
        jFileChooser.setFileFilter(new FileFilter() {

            private final String NES = ".NES";

            @Override
            public boolean accept(File f) {
                return f.isDirectory() || f.getAbsolutePath().toUpperCase().endsWith(NES);
            }

            @Override
            public String getDescription() {
                return NES;
            }
        });
        jFileChooser.setDialogTitle("Choose your destiny:");
        jFileChooser.showOpenDialog(null);

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.pack();
        this.setVisible(true);
    }

    public File getSelectedFile() {
        return jFileChooser.getSelectedFile();
    }
}
